package com.yonyou.pmclouds.procedure.rmiimpl;

import com.alibaba.dubbo.config.annotation.Service;
import com.yonyou.pmclouds.ApplicationConsts;
import com.yonyou.pmclouds.basecom.bp.processor.AuditInfoInsertProcessor;
import com.yonyou.pmclouds.basecom.bp.processor.AuditInfoUpdateProcessor;
import com.yonyou.pmclouds.basecom.bp.template.SingleDeleteBpTemplate;
import com.yonyou.pmclouds.basecom.bp.template.SingleInsertBpTemplate;
import com.yonyou.pmclouds.basecom.bp.template.SingleUpdateBpTemplate;
import com.yonyou.pmclouds.basecom.consts.EnableStatusConst;
import com.yonyou.pmclouds.basecom.exception.BusinessException;
import com.yonyou.pmclouds.basecom.exception.ConcurrentOperationException;
import com.yonyou.pmclouds.basecom.lock.AutoReleaseLockUtil;
import com.yonyou.pmclouds.basecom.ref.itf.RefCodeConst;
import com.yonyou.pmclouds.basecom.ref.itf.ReferenceChecker;
import com.yonyou.pmclouds.basecom.runtime.RuntimeEnvironment;
import com.yonyou.pmclouds.basecom.util.CodeGenerateUtil;
import com.yonyou.pmclouds.basecom.util.CommonFieldConst;
import com.yonyou.pmclouds.industryandtype.mapper.TypeDocMapper;
import com.yonyou.pmclouds.procedure.entity.ProcedureVO;
import com.yonyou.pmclouds.procedure.entity.ProceprojectVO;
import com.yonyou.pmclouds.procedure.mapper.ProcedureMapper;
import com.yonyou.pmclouds.procedure.processor.ProcedureDeleteRefCheck;
import com.yonyou.pmclouds.procedure.processor.ProcedureExtendCodeCheck;
import com.yonyou.pmclouds.procedure.processor.ProcedureInsertRefCheck;
import com.yonyou.pmclouds.procedure.processor.ProcedureMultiInsertSetRootPk;
import com.yonyou.pmclouds.procedure.rmiitf.ProcedureMaintain;
import com.yonyou.pmclouds.procedure.rmiitf.ProceprojectQryService;
import com.yonyou.pmclouds.workquality.service.rmiitf.WorkqualityQueryService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * 工序档案维护
 * <br>
 * 由于出现引用校验，刻意调大超时时间，降低超时的可能性
 */
@Service(interfaceClass = ProcedureMaintain.class, version = ApplicationConsts.APPLICATION_VERSION)
public class ProcedureMaintainImpl implements ProcedureMaintain {
    public static final String LOCK_PREFIX = "procedure_";
    private static final String[] UPDATE_FIELDS = new String[]{CommonFieldConst.SNAME,CommonFieldConst.SCODE};
    //导入类型：运营平台预置工序数据
    public static final int IMPORT_TYPE_OPERATION_PLATFORM = 1;
    //导入类型：EXCEL自定义工序数据
    public static final int IMPORT_TYPE_EXCEL = 2;
    //工序导入单节点下最大子工序数量
    private static final int MAX_CHILD_PER_NODE = 3844;

    @Autowired
    private ProcedureMapper mapper;
    @Autowired
    private ReferenceChecker referenceChecker;
    @Autowired
    private WorkqualityQueryService qualityQuery;
    @Autowired
    private TypeDocMapper typeDocMapper;
    @Autowired
    private ProceprojectQryService proceprojectQryService;

    private ProcedureInsertRefCheck insertRefCheck = new ProcedureInsertRefCheck(this);
    private ProcedureDeleteRefCheck deleteRefCheck = new ProcedureDeleteRefCheck(this);

    @Override
    public String insert(ProcedureVO procedureVO) throws BusinessException {
        //  add lock
        if (!AutoReleaseLockUtil.addAutoReleaseLock(LOCK_PREFIX + procedureVO.getPkTenant())) {
            throw new ConcurrentOperationException();
        }
        // 校验上级编码、校验编码重复、校验级次
        SingleInsertBpTemplate<ProcedureVO> bp = new SingleInsertBpTemplate<>(mapper);
        bp.addBeforeProcessor(new ProcedureExtendCodeCheck(mapper));
        //bp.addBeforeProcessor(insertRefCheck);  不在做引用校验，引用仍可增加下级
        // 添加审计信息
        bp.addBeforeProcessor(new AuditInfoInsertProcessor<ProcedureVO>());
        bp.insert(procedureVO);
        return procedureVO.getPkProcedure();
    }

    @Override
    public String update(ProcedureVO procedureVO) throws BusinessException {
        ProcedureVO oldProcedure = mapper.getById(procedureVO.getPkProcedure());

        ProcedureVO[] procedureVOS = mapper.querySelfAndChildrenByCodeAndStatus(oldProcedure.getScode(),
                oldProcedure.getPkTenant(), EnableStatusConst.ALL);

        SingleUpdateBpTemplate<ProcedureVO> bp = new SingleUpdateBpTemplate<>(mapper, UPDATE_FIELDS);
        bp.addBeforeProcessor(new ProcedureExtendCodeCheck(mapper));
        //审计信息
        bp.addBeforeProcessor(new AuditInfoUpdateProcessor<ProcedureVO>());
        //修改父节点
        procedureVO.setPkTenant(oldProcedure.getPkTenant());
        bp.update(procedureVO);
        //修改子孙节点
        for (ProcedureVO tempProcedure : procedureVOS) {
            if(tempProcedure.getPkProcedure().equals(procedureVO.getPkProcedure())) continue;

            tempProcedure.setScode(tempProcedure.getScode().replaceFirst(oldProcedure.getScode(),procedureVO.getScode()));

            bp.update(tempProcedure);
        }

        return procedureVO.getPkProcedure();
    }

    @Override
    public String delete(String procedureid, String changeFlag) throws BusinessException {
        //  add lock
        if (!AutoReleaseLockUtil.addAutoReleaseLock(LOCK_PREFIX + RuntimeEnvironment.getTenantId())) {
            throw new ConcurrentOperationException();
        }
        ProcedureVO vo = new ProcedureVO();
        vo.setPkProcedure(procedureid);
        vo.setChangeFlag(changeFlag);
        SingleDeleteBpTemplate<ProcedureVO> bp = new SingleDeleteBpTemplate<>(mapper);

        bp.addBeforeProcessor(deleteRefCheck);

        bp.delete(vo);
        return vo.getPkProcedure();
    }

    /**
     * 用户导入，选中的类型对应的预置的工序模板
     *
     * @param list
     * @return
     * @throws BusinessException
     */
    @Override
    public String[] insertPresetProcedures(List<ProcedureVO> list) throws BusinessException {

        List<ProcedureVO> allVOS = new ArrayList<>();   //避免再次请求数据库。直接把节点都取出来

        ProcedureVO[] rootVOS = getProceduresByRootsAndSetTenant(list.toArray(new ProcedureVO[]{}), allVOS); //得到需要导入的工序的根节点并设置租户主键

        return insertMulti(rootVOS, allVOS, IMPORT_TYPE_OPERATION_PLATFORM);
    }

    /**
     * Excel导入工序
     * @param list 根工序(树)的列表
     * @return 所有被插入的工序的id数组
     */
    @Override
    public String[] insertExcelProcedures(List<ProcedureVO> list) throws BusinessException {
        if (CollectionUtils.isEmpty(list)) {
            throw new BusinessException("工序导入失败，缺少工序信息");
        }

        List<ProcedureVO> allVOS = new ArrayList<>();   //展开的所有工序记录
        ProcedureVO[] rootVOS = getProceduresByRootsAndSetTenant(list.toArray(new ProcedureVO[]{}), allVOS);    //设置租户和状态

        return insertMulti(rootVOS, allVOS, IMPORT_TYPE_EXCEL);
    }

    @Override
    public ProcedureVO enable(ProcedureVO procedureVO) throws BusinessException {
        // 判断父节点是否启用
        if (StringUtils.isNotEmpty(procedureVO.getPkParent()) && !isParentEnable(procedureVO.getPkParent())) {
            throw new BusinessException("所选数据的父节点为停用状态，请先启用父节点。");
        }

        SingleUpdateBpTemplate<ProcedureVO> bp = new SingleUpdateBpTemplate<>(mapper, new String[]{EnableStatusConst.ENABLE_STATUS});
        bp.addBeforeProcessor(new AuditInfoUpdateProcessor<ProcedureVO>());
        procedureVO.setEnableStatus(EnableStatusConst.ENABLE);
        return bp.update(procedureVO);
    }

    /**
     * 判断父节点是否启用
     *
     * @return
     */
    private boolean isParentEnable(String pkParent) {
        ProcedureVO parentVO = mapper.getById(pkParent);
        if (parentVO != null && EnableStatusConst.ENABLE == parentVO.getEnableStatus())
            return true;

        return false;
    }

    @Override
    public ProcedureVO disable(ProcedureVO procedureVO) throws BusinessException {
        // 加锁
        if (!AutoReleaseLockUtil.addAutoReleaseLock(LOCK_PREFIX + procedureVO.getPkTenant())) {
            throw new ConcurrentOperationException();
        }

        // 递归停用节点及其所有子节点
        SingleUpdateBpTemplate<ProcedureVO> bp = new SingleUpdateBpTemplate<>(mapper, new String[]{EnableStatusConst.ENABLE_STATUS});
        bp.addBeforeProcessor(new AuditInfoUpdateProcessor<ProcedureVO>());
        disableRecursionProcedure(procedureVO, bp);

        return procedureVO;
    }

    /**
     * 递归停用当前节点及其所有子节点
     *
     * @param procedureVO
     * @param bp
     */
    private void disableRecursionProcedure(ProcedureVO procedureVO, SingleUpdateBpTemplate<ProcedureVO> bp) throws BusinessException {
        // 判断节点状态并停用
        if (EnableStatusConst.ENABLE == procedureVO.getEnableStatus()) {
            procedureVO.setEnableStatus(EnableStatusConst.DISABLE);
            bp.update(procedureVO);
        }

        ProcedureVO[] childrenVOs = procedureVO.getChildProcedure();
        if (ArrayUtils.isEmpty(childrenVOs)) return;

        for (ProcedureVO childVO : childrenVOs) {
            disableRecursionProcedure(childVO, bp);
        }
    }

    /**
     * 类内部使用，批量插入工序节点
     * 用于用户导入工序（运营平台数据或EXCEL数据）
     * @param rootVOS 根工序(树)的数组
     * @param allVOS 展开的所有工序的列表
     * @param type 导入类型，IMPORT_TYPE_OPERATION_PLATFORM；IMPORT_TYPE_EXCEL
     * @return 成功插入的工序id的数组
     * @throws BusinessException 业务异常
     */
    private String[] insertMulti(ProcedureVO[] rootVOS, List<ProcedureVO> allVOS, int type) throws BusinessException {
        if (ArrayUtils.isEmpty(rootVOS) || rootVOS[0] == null) return new String[0];
        //add lock
        if (!AutoReleaseLockUtil.addAutoReleaseLock(LOCK_PREFIX + rootVOS[0].getPkTenant())) {
            throw new ConcurrentOperationException();
        }

        if (type==IMPORT_TYPE_OPERATION_PLATFORM) {
            checkUnique(allVOS);  //校验需要导入工序的编码和名字唯一性
        } else if (type==IMPORT_TYPE_EXCEL) {
            checkUnique(rootVOS);
        } else {
            throw new BusinessException("不支持的导入操作");
        }

        SingleInsertBpTemplate<ProcedureVO> bp = new SingleInsertBpTemplate<>(mapper);
        bp.addBeforeProcessor(new AuditInfoInsertProcessor<ProcedureVO>());  //添加审计信息
        bp.addBeforeProcessor(new ProcedureMultiInsertSetRootPk());  //设置根节点pk_root
        List<String> resultList = new ArrayList<>();
        for (ProcedureVO rootVO : rootVOS) {
            resultList.addAll(bfsInsertProcedure(rootVO, bp));   //bfs序插入所有工序树并维护树结构
        }
        return resultList.toArray(new String[resultList.size()]);
    }

    /**
     * 类内部使用辅助方法，层序插入工序节点，并更新节点的父节点主键和根节点主键
     * 根节点的pk_root需要在processor中赋值（类：ProcedureMultiInsertSetRootPk）
     *
     * @param rootVO
     * @param bp
     * @throws BusinessException
     */
    private List<String> bfsInsertProcedure(ProcedureVO rootVO, SingleInsertBpTemplate<ProcedureVO> bp) throws BusinessException {
        List<String> resultList = new ArrayList<>();
        if (rootVO == null) return resultList;
        Queue<ProcedureVO> helpQueue = new LinkedList<>();
        helpQueue.offer(rootVO);
        while (!helpQueue.isEmpty()) {
            ProcedureVO vo = helpQueue.poll();
            bp.insert(vo);
            resultList.add(vo.getPkProcedure());
            ProcedureVO[] childVOS = vo.getChildProcedure();
            if (childVOS == null) continue;

            int width = Integer.toString(childVOS.length).length();
            if (width<2) {//下级工序默认编码长度比上级多2位
                width=2;
            }
            String codeFormat = "%0"+ width + "d";

            for (int i = 0; i < childVOS.length; i++) {
                ProcedureVO childVO = childVOS[i];
                if (childVO != null) {
                    childVO.setPkParent(vo.getPkProcedure());
                    childVO.setPkRoot(vo.getPkRoot());    //设置非根节点的pk_root
                    if (StringUtils.isEmpty(childVO.getScode())) {//从Excel导入的子工序尚未有code
                        childVO.setScode(vo.getScode()+String.format(codeFormat, i+1));
                    }
                    helpQueue.offer(childVO);
                }
            }

/*         for (ProcedureVO childVO : childVOS) {
                if (childVO != null) {
                    childVO.setPkParent(vo.getPkProcedure());
                    childVO.setPkRoot(vo.getPkRoot());    //设置非根节点的pk_root
                    helpQueue.offer(childVO);
                }
            }*/
        }
        return resultList;
    }

    /**
     * 类内部使用方法，（批量的）根据根节点，查询所有工序节点并设置所有节点的租户主键，构造成树，返回所有树的根节点
     *
     * @return
     */
    private ProcedureVO[] getProceduresByRootsAndSetTenant(ProcedureVO[] vos, List<ProcedureVO> allVOS) {
        //根据根节点主键，查询工序节点
        if (ArrayUtils.isEmpty(vos)) {
            return new ProcedureVO[0];
        }

        for(ProcedureVO vo : vos )
        {
            String pkTenant = RuntimeEnvironment.getTenantId();
            vo.setPkTenant(pkTenant);
            vo.setEnableStatus(EnableStatusConst.ENABLE);
            allVOS.add(vo);
            if(vo.getChildProcedure()!=null&&vo.getChildProcedure().length>0)
            {
                getProceduresByRootsAndSetTenant(vo.getChildProcedure(),allVOS);
            }
        }

        return vos;
    }

    /**
     * 类内部使用，批量校验工序编码和名字的唯一性
     *
     * @param vos
     * @throws BusinessException
     */
    private void checkUnique(List<ProcedureVO> vos) throws BusinessException {
        if (vos == null || vos.size() == 0 || vos.get(0) == null) return;
        String pkTenant = vos.get(0).getPkTenant();
        List<String> codes = new ArrayList<>();
        List<String> names = new ArrayList<>();
        for (ProcedureVO vo : vos) {
            if (vo != null) {
                codes.add(vo.getScode());
                names.add(vo.getSname());
            }
        }
        String[] conflictCodes = mapper.checkCodes(pkTenant, codes.toArray(new String[codes.size()])); //冲突的编码
        String[] conflictNames = mapper.checkNames(pkTenant, names.toArray(new String[names.size()])); //冲突的名字
        if (!ArrayUtils.isEmpty(conflictCodes) || !ArrayUtils.isEmpty(conflictNames)) {  //存在冲突
            StringBuffer errorMsg = new StringBuffer();
            //错误信息中添加冲突的编码
            if (!ArrayUtils.isEmpty(conflictCodes)) {
                for (int i = 0; i < conflictCodes.length; i++) {
                    if (!StringUtils.isEmpty(conflictCodes[i])) {
                        errorMsg.append(conflictCodes[i]);
                        errorMsg.append((i == conflictCodes.length - 1 && ArrayUtils.isEmpty(conflictNames) ? "。" : "，"));
                    }
                }
            }
            //错误信息中添加冲突的名字
            if (!ArrayUtils.isEmpty(conflictNames)) {
                for (int i = 0; i < conflictNames.length; i++) {
                    if (!StringUtils.isEmpty(conflictNames[i])) {
                        errorMsg.append(conflictNames[i]);
                        errorMsg.append(i == conflictNames.length - 1 ? "。" : "，");
                    }
                }
            }
            throw new BusinessException(errorMsg.toString(), "名字编码冲突");
        }
    }

    /**
     * 校验根工序名称是否已存在，给所有根工序生成不存在的工序编码
     * @param rootVos 根工序(树)的数组
     * @throws BusinessException 业务异常
     */
    private void checkUnique(ProcedureVO[] rootVos) throws BusinessException {
        if (rootVos==null||rootVos.length==0||rootVos[0]==null) {
            throw new BusinessException("工序导入出错，缺少导入数据");
        }

        if (rootVos.length>MAX_CHILD_PER_NODE) {
            throw new BusinessException("导入一级工序过多，请按照一级工序拆分为多个EXCEL进行操作");
        }

        String pkTenant = rootVos[0].getPkTenant();
        List<String> names = new ArrayList<>();
        for (ProcedureVO vo : rootVos) {
            if (vo != null) {
                names.add(vo.getSname());
            }
        }
        String[] conflictNames = mapper.checkNames(pkTenant, names.toArray(new String[names.size()])); //冲突的名字
        if (ArrayUtils.isNotEmpty(conflictNames)) {
            StringBuilder sb = new StringBuilder();
            sb.append("一级工序名称：");
            for (String name : conflictNames) {
                sb.append(name);
                sb.append(" ");
            }
            sb.append("已存在，请检查");
            throw new BusinessException(sb.toString());
        }

        String sCodePrefix = CodeGenerateUtil.randomBase62Code(4);
        int retry = 0;
        int count = mapper.countCodeLike(pkTenant, sCodePrefix);
        while (count>0 && retry < 99) {//暂定最大尝试100次
            sCodePrefix = CodeGenerateUtil.randomBase62Code(4);
            count = mapper.countCodeLike(pkTenant, sCodePrefix);
            retry++;
        }

        if (count>0) {
            throw new BusinessException("导入工序，生成工序编码出错，请稍后尝试。");
        }

        int width = Integer.toString(rootVos.length).length();
        if (width<2) {
            width=2;
        }
        String codeFormat = "%0"+ width + "d";
        for (int i = 0; i < rootVos.length; i++) {
            rootVos[i].setScode(sCodePrefix+String.format(codeFormat, i+1));
        }
    }

    @Override
    public int getProcedureRefStatus(String procedureid, String tenantid) throws BusinessException {
        // 子节点引用校验
        int childCount = mapper.countChild(procedureid);
        if (childCount > 0) {
            return STATUS_REF_BY_CHILD;
        }
        //  校验是否被标准引用
        if (qualityQuery.queryByProcedure(procedureid) != null) {
            return STATUS_REF_BY_QUALITY;
        }

        //检验是否被项目档案引用
        ProceprojectVO proceprojectVO = new ProceprojectVO();
        proceprojectVO.setPkProcedure(procedureid);
        ProceprojectVO proceprojectVO1 = proceprojectQryService.selectProceprojectByObj(proceprojectVO);
        if (proceprojectVO1!=null){
            return STATUS_REF_BY_PROJECT;//
        }

        if (referenceChecker.checkReferenced(RefCodeConst.PROCEDURE, new String[]{procedureid})) {
            return STATUS_REF_BY_BILL;
        }
        return STATUS_NOT_REF;
    }

    @Override
    public boolean referenceCheck(String procedureid) throws BusinessException {
        if (referenceChecker.checkReferenced(RefCodeConst.PROCEDURE, new String[]{procedureid})
                   || qualityQuery.queryByProcedure(procedureid) != null) {
            return true;
        }
        return false;
    }

    public static final int STATUS_NOT_REF = 0;
    public static final int STATUS_REF_BY_CHILD = 1;
    public static final int STATUS_REF_BY_QUALITY = 2;
    public static final int STATUS_REF_BY_BILL = 3;
    public static final int STATUS_REF_BY_PROJECT = 4;

}
